package com.inplaceedit.client.activity.main.contact;

import java.util.ArrayList;
import java.util.Date;
import java.util.List;

import com.google.gwt.activity.shared.AbstractActivity;
import com.google.gwt.core.client.GWT;
import com.google.gwt.editor.client.Editor;
import com.google.gwt.editor.client.LeafValueEditor;
import com.google.gwt.editor.client.SimpleBeanEditorDriver;
import com.google.gwt.editor.client.adapters.ListEditor;
import com.google.gwt.event.shared.EventBus;
import com.google.gwt.place.shared.PlaceHistoryMapper;
import com.google.gwt.user.client.rpc.AsyncCallback;
import com.google.gwt.user.client.ui.AcceptsOneWidget;
import com.google.gwt.user.client.ui.IsWidget;
import com.google.inject.Inject;
import com.core.gwt.place.client.AbstractMode;
import com.core.gwt.place.client.Mode;
import com.core.gwt.place.client.ModeManager;
import com.inplaceedit.client.place.CompanyDetailsPlace;
import com.inplaceedit.client.place.ContactDetailsPlace;
import com.inplaceedit.client.ui.SaveCancelDialog;
import com.inplaceedit.client.ui.contact.ContactNameDisplay;
import com.inplaceedit.client.ui.contact.ContactNameEditor;
import com.inplaceedit.client.ui.note.NoteEditor;
import com.inplaceedit.client.ui.note.NoteListDisplay;
import com.inplaceedit.client.ui.phone.PhoneEditor;
import com.inplaceedit.client.ui.phone.PhoneListDisplay;
import com.inplaceedit.client.ui.support.AutoBeanTools;
import com.inplaceedit.shared.dto.CompanyDto;
import com.inplaceedit.shared.dto.ContactDto;
import com.inplaceedit.shared.dto.DtoFactory;
import com.inplaceedit.shared.dto.NoteDto;
import com.inplaceedit.shared.dto.PhoneDto;
import com.inplaceedit.shared.webservice.CompanyRemoteServiceAsync;
import com.inplaceedit.shared.webservice.ContactRemoteServiceAsync;

public class ContactDetailsActivityImpl extends AbstractActivity implements
		ContactDetailsActivity {

	public static interface NoteListHolder {
		List<NoteDto> getValue();
	}

	public static interface NoteListHolderDisplay extends
			Editor<NoteListHolder> {
		@Editor.Path("value")
		ListEditor<NoteDto, NoteEditor> getDisplay();
	}

	public static interface ObjectBirthdayEditor extends Editor<ContactDto> {
		@Editor.Path("birthday")
		LeafValueEditor<Date> getEditor();
	}

	public static interface ObjectDisplay extends Editor<ContactDto> {
		@Editor.Path("birthday")
		LeafValueEditor<Date> getBirthdayDisplay();

		@Editor.Path("")
		ContactNameDisplay getNameDisplay();

		@Editor.Path("phoneList")
		PhoneListDisplay getPhoneListDisplay();

		@Editor.Path("sex")
		LeafValueEditor<ContactDto.Sex> getSexDisplay();
	}

	public static interface ObjectNameEditor extends Editor<ContactDto> {
		@Editor.Path("")
		ContactNameEditor getEditor();
	}

	public static interface ObjectSexEditor extends Editor<ContactDto> {
		@Editor.Path("sex")
		LeafValueEditor<ContactDto.Sex> getEditor();
	}

	public static interface View extends IsWidget, ObjectDisplay {
		public static interface Handler {
			void onEditBirthday();

			void onEditName();

			void onEditSex();

			void onNewNote();

			void onNewPhone();
		}

		SaveCancelDialog getBirthdayDialog();

		ObjectBirthdayEditor getBirthdayEditor();

		SaveCancelDialog getNameDialog();

		ObjectNameEditor getNameEditor();

		SaveCancelDialog getNewNoteDialog();

		NoteEditor getNewNoteEditor();

		SaveCancelDialog getNewPhoneDialog();

		PhoneEditor getNewPhoneEditor();

		NoteListDisplay getNoteListDisplay();

		SaveCancelDialog getSexDialog();

		ObjectSexEditor getSexEditor();

		void hideDialogs();

		void setHandler(Handler handler);

		void showDetails(boolean show);
	}

	interface NoteEditorDriver extends
			SimpleBeanEditorDriver<NoteDto, NoteEditor> {
	}

	interface NoteListHolderDisplayDriver extends
			SimpleBeanEditorDriver<NoteListHolder, NoteListHolderDisplay> {
	}

	interface ObjectBirthdayEditorDriver extends
			SimpleBeanEditorDriver<ContactDto, ObjectBirthdayEditor> {
	}

	interface ObjectDisplayDriver extends
			SimpleBeanEditorDriver<ContactDto, ObjectDisplay> {
	}

	interface ObjectNameEditorDriver extends
			SimpleBeanEditorDriver<ContactDto, ObjectNameEditor> {
	}

	interface ObjectSexEditorDriver extends
			SimpleBeanEditorDriver<ContactDto, ObjectSexEditor> {
	}

	interface PhoneEditorDriver extends
			SimpleBeanEditorDriver<PhoneDto, PhoneEditor> {
	}

	private class BirthdayEditMode extends AbstractMode {
		private ObjectBirthdayEditorDriver driver;

		private final SaveCancelDialog.Handler dlgHandler = new SaveCancelDialog.Handler() {
			@Override
			public void onCancel() {
				exit();
			}

			@Override
			public void onSave() {
				final ContactDto object = driver.flush();
				contactService.save(object, new AsyncCallback<ContactDto>() {
					@Override
					public void onFailure(final Throwable caught) {
						// TODO: handle validation failures
					}

					@Override
					public void onSuccess(final ContactDto result) {
						displayDriver.edit(result);
						exit();
					}
				});
			}
		};

		@Override
		public String mayCancel() {
			String warning = null;
			if (driver != null && driver.isDirty()) {
				warning = "Discard changes?";
			}
			return warning;
		}

		@Override
		protected void onEnter() {
			if (driver == null) {
				driver = GWT.create(ObjectBirthdayEditorDriver.class);
				driver.initialize(view.getBirthdayEditor());
			}
			driver.edit(AutoBeanTools.clone(displayDriver.flush(),
					ContactDto.class));
			view.getBirthdayDialog().show(dlgHandler);
		}

		@Override
		protected void onExit() {
			view.getBirthdayDialog().hide();
		}
	}

	private class NameEditMode extends AbstractMode {
		private ObjectNameEditorDriver driver;

		private final SaveCancelDialog.Handler dlgHandler = new SaveCancelDialog.Handler() {
			@Override
			public void onCancel() {
				exit();
			}

			@Override
			public void onSave() {
				final ContactDto object = driver.flush();
				contactService.save(object, new AsyncCallback<ContactDto>() {
					@Override
					public void onFailure(final Throwable caught) {
						// TODO: handle validation failures
					}

					@Override
					public void onSuccess(final ContactDto result) {
						displayDriver.edit(result);
						exit();
					}
				});
			}
		};

		@Override
		public String mayCancel() {
			String warning = null;
			if (driver != null && driver.isDirty()) {
				warning = "Discard changes?";
			}
			return warning;
		}

		@Override
		protected void onEnter() {
			if (driver == null) {
				driver = GWT.create(ObjectNameEditorDriver.class);
				driver.initialize(view.getNameEditor());
			}
			driver.edit(AutoBeanTools.clone(displayDriver.flush(),
					ContactDto.class));
			view.getNameEditor().getEditor().refresh();
			view.getNameDialog().show(dlgHandler);
		}

		@Override
		protected void onExit() {
			view.getNameDialog().hide();
		}
	}

	private class NewNoteMode extends AbstractMode {
		private NoteEditorDriver driver;
		private final SaveCancelDialog.Handler dlgHandler = new SaveCancelDialog.Handler() {
			@Override
			public void onCancel() {
				exit();
			}

			@Override
			public void onSave() {
				final NoteDto object = driver.flush();
				contactService.saveNote(object, new AsyncCallback<NoteDto>() {
					@Override
					public void onFailure(final Throwable caught) {
						// TODO Handle errors
					}

					@Override
					public void onSuccess(final NoteDto result) {
						loadNotes();
						exit();
					}
				});
			}
		};

		@Override
		public String mayCancel() {
			String warning = null;
			if (driver != null && driver.isDirty()) {
				warning = "Discard changes?";
			}
			return warning;
		}

		@Override
		protected void onEnter() {
			if (driver == null) {
				driver = GWT.create(NoteEditorDriver.class);
				driver.initialize(view.getNewNoteEditor());
			}

			final NoteDto object = factory.createNote().as();
			object.setContact(factory.createContact().as());
			object.getContact().setId(place.getId());
			object.setTime(new Date());

			driver.edit(object);
			view.getNewNoteDialog().show(dlgHandler);
		}

		@Override
		protected void onExit() {
			view.getNewNoteDialog().hide();
		}

	}

	private class NewPhoneMode extends AbstractMode {
		private PhoneEditorDriver driver;

		private final SaveCancelDialog.Handler dlgHandler = new SaveCancelDialog.Handler() {
			@Override
			public void onCancel() {
				exit();
			}

			@Override
			public void onSave() {
				final ContactDto contact = AutoBeanTools.clone(
						displayDriver.flush(), ContactDto.class);
				// Some kind of bug in the AutoBean list implementation forces
				// me to duplicate the phone list as a plain ArrayList
				contact.setPhoneList(new ArrayList<PhoneDto>(contact
						.getPhoneList()));
				contact.getPhoneList().add(driver.flush());
				contactService.save(contact, new AsyncCallback<ContactDto>() {
					@Override
					public void onFailure(final Throwable caught) {
						// TODO Auto-generated method stub
					}

					@Override
					public void onSuccess(final ContactDto result) {
						displayDriver.edit(result);
						exit();
					}
				});
			}
		};

		@Override
		public String mayCancel() {
			String warning = null;
			if (driver != null && driver.isDirty()) {
				warning = "Discard changes?";
			}
			return warning;
		}

		@Override
		protected void onEnter() {
			if (driver == null) {
				driver = GWT.create(PhoneEditorDriver.class);
				driver.initialize(view.getNewPhoneEditor());
			}
			driver.edit(factory.createPhone().as());
			view.getNewPhoneDialog().show(dlgHandler);
		}

		@Override
		protected void onExit() {
			view.getNewPhoneDialog().hide();
		}
	}

	private class NoteEditMode extends AbstractMode {
		private final NoteDto object;
		private final NoteEditor editor;
		private final SaveCancelDialog dialog;
		private NoteEditorDriver driver;

		private final SaveCancelDialog.Handler dlgHandler = new SaveCancelDialog.Handler() {
			@Override
			public void onCancel() {
				exit();
			}

			@Override
			public void onSave() {
				final NoteDto object = driver.flush();
				contactService.saveNote(object, new AsyncCallback<NoteDto>() {
					@Override
					public void onFailure(final Throwable caught) {
						// TODO Handle errors
					}

					@Override
					public void onSuccess(final NoteDto result) {
						loadNotes();
						exit();
					}
				});
			}
		};

		public NoteEditMode(final NoteDto object, final NoteEditor editor,
				final SaveCancelDialog dialog) {
			this.object = object;
			this.editor = editor;
			this.dialog = dialog;

		}

		@Override
		public String mayCancel() {
			String warning = null;
			if (driver.isDirty()) {
				warning = "Discard changes?";
			}
			return warning;
		}

		@Override
		protected void onEnter() {
			driver = GWT.create(NoteEditorDriver.class);
			driver.initialize(editor);
			driver.edit(AutoBeanTools.clone(object, NoteDto.class));
			dialog.show(dlgHandler);
		}

		@Override
		protected void onExit() {
			dialog.hide();
		}
	}

	private class PhoneEditMode extends AbstractMode {
		private final int index;
		private final PhoneEditor editor;
		private final SaveCancelDialog dialog;

		private PhoneEditorDriver driver;

		private final SaveCancelDialog.Handler dlgHandler = new SaveCancelDialog.Handler() {
			@Override
			public void onCancel() {
				exit();
			}

			@Override
			public void onSave() {
				final ContactDto contact = AutoBeanTools.clone(
						displayDriver.flush(), ContactDto.class);
				final PhoneDto phone = driver.flush();
				contact.getPhoneList().set(index, phone);

				contactService.save(contact, new AsyncCallback<ContactDto>() {
					@Override
					public void onFailure(final Throwable caught) {
						// TODO Auto-generated method stub
					}

					@Override
					public void onSuccess(final ContactDto result) {
						displayDriver.edit(result);
						exit();
					}
				});
			}
		};

		public PhoneEditMode(final int index, final PhoneEditor editor,
				final SaveCancelDialog dialog) {
			super();
			this.index = index;
			this.editor = editor;
			this.dialog = dialog;
		}

		@Override
		public String mayCancel() {
			String warning = null;
			if (driver.isDirty()) {
				warning = "Discard changes?";
			}
			return warning;
		}

		@Override
		protected void onEnter() {
			final ContactDto contact = displayDriver.flush();
			final PhoneDto phone = contact.getPhoneList().get(index);

			driver = GWT.create(PhoneEditorDriver.class);
			driver.initialize(editor);
			driver.edit(AutoBeanTools.clone(phone, PhoneDto.class));

			dialog.show(dlgHandler);
		}

		@Override
		protected void onExit() {
			dialog.hide();
		}

	}

	private class SexEditMode extends AbstractMode {
		private ObjectSexEditorDriver driver;

		private final SaveCancelDialog.Handler dlgHandler = new SaveCancelDialog.Handler() {
			@Override
			public void onCancel() {
				exit();
			}

			@Override
			public void onSave() {
				final ContactDto object = driver.flush();
				contactService.save(object, new AsyncCallback<ContactDto>() {
					@Override
					public void onFailure(final Throwable caught) {
						// TODO: handle validation failures
					}

					@Override
					public void onSuccess(final ContactDto result) {
						displayDriver.edit(result);
						exit();
					}
				});
			}
		};

		@Override
		public String mayCancel() {
			String warning = null;
			if (driver != null && driver.isDirty()) {
				warning = "Discard changes?";
			}
			return warning;
		}

		@Override
		protected void onEnter() {
			if (driver == null) {
				driver = GWT.create(ObjectSexEditorDriver.class);
				driver.initialize(view.getSexEditor());
			}
			driver.edit(AutoBeanTools.clone(displayDriver.flush(),
					ContactDto.class));
			view.getSexDialog().show(dlgHandler);
		}

		@Override
		protected void onExit() {
			view.getSexDialog().hide();
		}
	}

	@Inject
	private View view;
	@Inject
	private ContactRemoteServiceAsync contactService;
	@Inject
	private CompanyRemoteServiceAsync companyService;
	@Inject
	private PlaceHistoryMapper placeMapper;

	private ContactDetailsPlace place;
	private final ModeManager modeManager = new ModeManager();
	private final ObjectDisplayDriver displayDriver = GWT
			.create(ObjectDisplayDriver.class);
	private final NoteListHolderDisplayDriver noteListDisplayDriver = GWT
			.create(NoteListHolderDisplayDriver.class);
	private final DtoFactory factory = GWT.create(DtoFactory.class);

	private final View.Handler viewHandler = new View.Handler() {
		@Override
		public void onEditBirthday() {
			modeManager.enterMode(birthdayEditMode);
		}

		@Override
		public void onEditName() {
			modeManager.enterMode(nameEditMode);
		}

		@Override
		public void onEditSex() {
			modeManager.enterMode(sexEditMode);
		}

		@Override
		public void onNewNote() {
			modeManager.enterMode(newNoteMode);
		}

		@Override
		public void onNewPhone() {
			modeManager.enterMode(newPhoneMode);
		}
	};

	private final Mode nameEditMode = new NameEditMode();
	private final Mode birthdayEditMode = new BirthdayEditMode();
	private final Mode sexEditMode = new SexEditMode();
	private final Mode newPhoneMode = new NewPhoneMode();
	private final Mode newNoteMode = new NewNoteMode();

	private final ContactNameDisplay.Handler nameDisplayHandler = new ContactNameDisplay.Handler() {
		@Override
		public String getHistoryToken(final CompanyDto object) {
			final CompanyDetailsPlace place = new CompanyDetailsPlace();
			place.setId(object.getId());
			return placeMapper.getToken(place);
		}
	};

	private final ContactNameEditor.Handler nameEditorHandler = new ContactNameEditor.Handler() {
		@Override
		public void getCompanyList(
				final AsyncCallback<List<CompanyDto>> callback) {
			companyService.list(callback);
		}
	};

	private final PhoneListDisplay.Handler phoneListDisplayHandler = new PhoneListDisplay.Handler() {
		@Override
		public void onDelete(final int index) {
			final ContactDto contact = AutoBeanTools.clone(
					displayDriver.flush(), ContactDto.class);
			// Some kind of bug in the AutoBean list implementation forces me to
			// duplicate the phone list as a plain ArrayList
			contact.setPhoneList(new ArrayList<PhoneDto>(contact.getPhoneList()));
			contact.getPhoneList().remove(index);
			contactService.save(contact, new AsyncCallback<ContactDto>() {
				@Override
				public void onFailure(final Throwable caught) {
					// TODO Auto-generated method stub
				}

				@Override
				public void onSuccess(final ContactDto result) {
					displayDriver.edit(result);
				}
			});
		}

		@Override
		public void onEdit(final int index, final PhoneEditor editor,
				final SaveCancelDialog dialog) {
			modeManager.enterMode(new PhoneEditMode(index, editor, dialog));
		}
	};

	private final NoteListDisplay.Handler noteListDisplayHandler = new NoteListDisplay.Handler() {
		@Override
		public void onDelete(final NoteDto object) {
			contactService.deleteNote(object, new AsyncCallback<NoteDto>() {
				@Override
				public void onFailure(final Throwable caught) {
					// TODO Auto-generated method stub
				}

				@Override
				public void onSuccess(final NoteDto result) {
					loadNotes();
				}
			});
		}

		@Override
		public void onEdit(final NoteDto object, final NoteEditor editor,
				final SaveCancelDialog dialog) {
			modeManager.enterMode(new NoteEditMode(object, editor, dialog));
		}
	};

	private final AsyncCallback<List<NoteDto>> loadNotesCallback = new AsyncCallback<List<NoteDto>>() {
		@Override
		public void onFailure(final Throwable caught) {
		}

		@Override
		public void onSuccess(final List<NoteDto> result) {
			noteListDisplayDriver.edit(new NoteListHolder() {
				@Override
				public List<NoteDto> getValue() {
					return result.subList(0, Math.min(result.size(), 50));
				}
			});
		}
	};

	@Override
	public String mayStop() {
		String warning = super.mayStop();
		if (warning == null) {
			warning = modeManager.mayStop();
		}
		return warning;
	}

	@Override
	public void onCancel() {
		super.onCancel();
		modeManager.onCancel();
		detach();
	}

	@Override
	public void onStop() {
		super.onStop();
		modeManager.onStop();
		detach();
	}

	@Override
	public void setPlace(final ContactDetailsPlace place) {
		this.place = place;
	}

	@Override
	public void start(final AcceptsOneWidget panel, final EventBus eventBus) {
		attach();

		displayDriver.initialize(view);
		noteListDisplayDriver.initialize(new NoteListHolderDisplay() {
			@Override
			public ListEditor<NoteDto, NoteEditor> getDisplay() {
				return view.getNoteListDisplay().asEditor();
			}
		});

		view.showDetails(false);
		view.hideDialogs();

		contactService.get(place.getId(), new AsyncCallback<ContactDto>() {
			@Override
			public void onFailure(final Throwable caught) {
				GWT.log("", caught);
			}

			@Override
			public void onSuccess(final ContactDto result) {
				if (result != null) {
					view.showDetails(true);
					displayDriver.edit(result);
				}
				panel.setWidget(view);
			}
		});

		loadNotes();
	}

	private void attach() {
		view.setHandler(viewHandler);
		view.getNameDisplay().setHandler(nameDisplayHandler);
		view.getNameEditor().getEditor().setHandler(nameEditorHandler);
		view.getPhoneListDisplay().setHandler(phoneListDisplayHandler);
		view.getNoteListDisplay().setHandler(noteListDisplayHandler);
	}

	private void detach() {
		view.setHandler(null);
		view.getNameDisplay().setHandler(null);
		view.getNameEditor().getEditor().setHandler(null);
		view.getPhoneListDisplay().setHandler(null);
		view.getNoteListDisplay().setHandler(null);
	}

	private void loadNotes() {
		contactService.listNotes(place.getId(), loadNotesCallback);
	}
}
